/**
 * 
 */
package com.db.stockexchange.common.parsing;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.db.stockexchange.common.OrderSide;
import com.db.stockexchange.common.OrderStatus;
import com.db.stockexchange.common.OrderType;
import com.db.stockexchange.common.messages.FIXMessage;
import com.db.stockexchange.common.messages.MessageType;
import com.db.stockexchange.common.messages.client.*;
import com.db.stockexchange.common.messages.server.*;

/**
 * @author Rayman
 * Represents methods for parsing messages of FIX protocol
 */
public class FIXParser {
	static final String FIELD_DELIMITER = "|";
	static final String KEY_VALUE_DELIMITER = "=";
	
	static final String MSG_TYPE = "35";
	static final String SENDER_COMP_ID = "49";
	static final String CL_ORD_ID = "11";
	static final String SYMBOL = "55";
	static final String SIDE = "54";
	static final String ORDER_QTY = "38";
	static final String ORD_TYPE = "40";
	static final String PRICE = "44";
	static final String CUM_QTY = "14";
	static final String LEAVES_QTY = "151";
	static final String LAST_PX = "31";
	static final String LAST_QTY = "32";
	static final String ORD_STATUS = "39";
	static final String ORIG_CL_ORD_ID = "41";
	static final String SESSION_REJECT_REASON = "373";
	static final String TEXT = "58";
	static final String PARTY_ID = "448";
	
	static final String CHECK_SUM = "10";
	
	static final String REG_END = "(\\" + FIELD_DELIMITER + "|\\s|$){0,1}";
	static final String REG_INT = "(\\d+)";
	static final String REG_FLOAT = "(\\d+(\\.\\d+){0,1})";
	static final String REG_ONE_CHAR = "([0-9A-Z]{1})";
	static final String REG_STRING = "(\\w+)";
	
	static final String REG_MSG_TYPE = MSG_TYPE + KEY_VALUE_DELIMITER + "([0-9]{1}|[a-zA-Z]{1,2})" + REG_END; // one digit or one or two letter, uppercase or lowercase
	static final String REG_SENDER_COMP_ID = SENDER_COMP_ID + KEY_VALUE_DELIMITER + REG_STRING + REG_END;
	// order ID is number value
	static final String REG_CL_ORD_ID = CL_ORD_ID + KEY_VALUE_DELIMITER + REG_INT + REG_END;
	static final String REG_SYMBOL = SYMBOL + KEY_VALUE_DELIMITER + REG_STRING + REG_END;
	static final String REG_SIDE = SIDE + KEY_VALUE_DELIMITER + REG_ONE_CHAR + REG_END;
	static final String REG_ORDER_QTY = ORDER_QTY + KEY_VALUE_DELIMITER + REG_INT + REG_END;
	static final String REG_ORD_TYPE = ORD_TYPE + KEY_VALUE_DELIMITER + REG_ONE_CHAR + REG_END;
	static final String REG_PRICE = PRICE + KEY_VALUE_DELIMITER + REG_FLOAT + REG_END;
	static final String REG_CUM_QTY = CUM_QTY + KEY_VALUE_DELIMITER + REG_INT + REG_END;
	static final String REG_LEAVES_QTY = LEAVES_QTY + KEY_VALUE_DELIMITER + REG_INT + REG_END;
	static final String REG_LAST_PX = LAST_PX + KEY_VALUE_DELIMITER + REG_FLOAT + REG_END;
	static final String REG_LAST_QTY = LAST_QTY + KEY_VALUE_DELIMITER + REG_INT + REG_END;
	static final String REG_ORD_STATUS = ORD_STATUS + KEY_VALUE_DELIMITER + REG_ONE_CHAR + REG_END;
	static final String REG_ORIG_CL_ORD_ID = ORIG_CL_ORD_ID + KEY_VALUE_DELIMITER + REG_INT + REG_END;
	static final String REG_CHECK_SUM = CHECK_SUM + KEY_VALUE_DELIMITER + REG_INT;
	static final String REG_SESSION_REJECT_REASON = SESSION_REJECT_REASON + KEY_VALUE_DELIMITER + "(\\d)" + REG_END;
	static final String REG_TEXT = TEXT + KEY_VALUE_DELIMITER + "([^\\|$]+)" + REG_END;
	static final String REG_PARTY_ID = PARTY_ID + KEY_VALUE_DELIMITER + REG_STRING + REG_END;
	
	public FIXMessage parseMessage(String message) throws FIXParseException {
		
		MessageType type = parseMessageType(message, REG_MSG_TYPE, MSG_TYPE);
		
		switch (type) {
		case LOGON:return parseLogon(message);
		case LOGOUT:return parseLogout(message);
		case NEW_ORDER:return parseNewOrder(message);
		case EXEC_REPORT:return parseExecReport(message);
		case CANCEL_ORDER:return parseCancelOrder(message);
		case REJECT:return parseReject(message);
			default:
				throw new FIXParseException(new RejectedMessage(RejectReason.INVALID_MSG_TYPE.asNumber(), 
						"Message type <" + type.asString() + "> is not supported"));
		}
	}

	private FIXMessage parseLogon(String message) throws FIXParseException {
		
		String senderCompId = parseString(message, REG_SENDER_COMP_ID, SENDER_COMP_ID);
		
		return new LogonMessage(senderCompId);
	}

	private FIXMessage parseLogout(String message) throws FIXParseException {

		String senderCompId = parseString(message, REG_SENDER_COMP_ID, SENDER_COMP_ID);
		
		return new LogoutMessage(senderCompId);
	}

	private FIXMessage parseNewOrder(String message) throws FIXParseException {

		String senderCompId = parseString(message, REG_SENDER_COMP_ID, SENDER_COMP_ID);
		int orderId = parseInteger(message, REG_CL_ORD_ID, CL_ORD_ID);
		String tickerName = parseString(message, REG_SYMBOL, SYMBOL);
		OrderSide side = parseSide(message, REG_SIDE, SIDE);
		int quantity = parseInteger(message, REG_ORDER_QTY, ORDER_QTY);
		OrderType type = parseType(message, REG_ORD_TYPE, ORD_TYPE);
		float price = Float.NaN;
		if (type == OrderType.LIMIT)
			price = parseFloat(message, REG_PRICE, PRICE);
		
		return new NewOrderMessage(senderCompId, orderId, tickerName, side, quantity, type, price);
	}

	private FIXMessage parseExecReport(String message) throws FIXParseException {
		OrderStatus status = parseOrderStatus(message, REG_ORD_STATUS, ORD_STATUS);
		
		switch (status) {
		case NEW: return parseAck(message);
		case PARTIALLY_FILLED:
		case FILLED: return parseFilled(message, status);
		case CANCELLED: return parseCancelled(message);
		case REJECTED: return parseRejected(message);
		default:
			throw new FIXParseException(new RejectedMessage(RejectReason.VALUE_IS_INCORRECT.asNumber(), 
					"Unsupported status of order <" + status + ">"));
		}
	}

	private FIXMessage parseAck(String message) throws FIXParseException {
		String senderCompId = parseString(message, REG_SENDER_COMP_ID, SENDER_COMP_ID);
		int orderId = parseInteger(message, REG_CL_ORD_ID, CL_ORD_ID);
		String tickerName = parseString(message, REG_SYMBOL, SYMBOL);
		OrderSide side = parseSide(message, REG_SIDE, SIDE);
		int quantity = parseInteger(message, REG_ORDER_QTY, ORDER_QTY);
		int cumQuantity = parseInteger(message, REG_CUM_QTY, CUM_QTY);
		
		int leavesQuantity = 0;
		try {
			leavesQuantity = parseInteger(message, REG_LEAVES_QTY, LEAVES_QTY);
		} catch (FIXParseException e) {}
		
		float price = parseFloat(message, REG_PRICE, PRICE);
		
		return new AckMessage(senderCompId, orderId, tickerName, side, quantity, cumQuantity, leavesQuantity, price);
	}

	private FIXMessage parseFilled(String message, OrderStatus status) throws FIXParseException {
		String senderCompId = parseString(message, REG_SENDER_COMP_ID, SENDER_COMP_ID);
		int orderId = parseInteger(message, REG_CL_ORD_ID, CL_ORD_ID);
		String tickerName = parseString(message, REG_SYMBOL, SYMBOL);
		OrderSide side = parseSide(message, REG_SIDE, SIDE);
		int quantity = parseInteger(message, REG_ORDER_QTY, ORDER_QTY);
		int cumQuantity = parseInteger(message, REG_CUM_QTY, CUM_QTY);
		
		int leavesQuantity = 0;
		try {
			leavesQuantity = parseInteger(message, REG_LEAVES_QTY, LEAVES_QTY);
		} catch (FIXParseException e) { }
		
		float price = parseFloat(message, REG_PRICE, PRICE);
		
		float lastPrice = 0;
		try {
			lastPrice = parseFloat(message, REG_LAST_PX, LAST_PX);
		} catch (FIXParseException e) { lastPrice = price; }
		
		int lastQuantity = 0;
		try {
			lastQuantity = parseInteger(message, REG_LAST_QTY, LAST_QTY);
		} catch (FIXParseException e) { }
		
		String partyId = "";
		try {
			partyId = parseString(message, REG_PARTY_ID, PARTY_ID);
		} catch (FIXParseException e) { }
		
		return new FilledMessage(senderCompId, orderId, tickerName, side, status, quantity, cumQuantity, leavesQuantity, price, lastPrice, lastQuantity, partyId);
	}
	
	private FIXMessage parseCancelled(String message) throws FIXParseException {
		String senderCompId = parseString(message, REG_SENDER_COMP_ID, SENDER_COMP_ID);
		int orderId = parseInteger(message, REG_CL_ORD_ID, CL_ORD_ID);
		int origOrderId = parseInteger(message, REG_ORIG_CL_ORD_ID, ORIG_CL_ORD_ID);
		String tickerName = parseString(message, REG_SYMBOL, SYMBOL);
		OrderSide side = parseSide(message, REG_SIDE, SIDE);
		float lastPrice = parseFloat(message, REG_LAST_PX, LAST_PX);
		int lastQuantity = parseInteger(message, REG_LAST_QTY, LAST_QTY);
		
		return new CancelledMessage(senderCompId, orderId, tickerName, side, origOrderId, lastPrice, lastQuantity);
	}

	private FIXMessage parseRejected(String message) throws FIXParseException {
		String senderCompId = parseString(message, REG_SENDER_COMP_ID, SENDER_COMP_ID);
		int orderId = parseInteger(message, REG_CL_ORD_ID, CL_ORD_ID);
		String tickerName = parseString(message, REG_SYMBOL, SYMBOL);
		OrderSide side = parseSide(message, REG_SIDE, SIDE);
		String detail = parseString(message, REG_TEXT, TEXT);
		
		return new ExecutionRejectMessage(senderCompId, orderId, tickerName, side, detail);
	}
	
	private FIXMessage parseReject(String message) throws FIXParseException {
		int rejectReason = parseInteger(message, REG_SESSION_REJECT_REASON, SESSION_REJECT_REASON);
		String rejectDetail = parseString(message, REG_TEXT, TEXT);
		
		return new RejectedMessage(rejectReason, rejectDetail);
	}

	private FIXMessage parseCancelOrder(String message) throws FIXParseException {

		String senderCompId = parseString(message, REG_SENDER_COMP_ID, SENDER_COMP_ID);
		int orderId = parseInteger(message, REG_CL_ORD_ID, CL_ORD_ID);
		int origOrderId = parseInteger(message, REG_ORIG_CL_ORD_ID, ORIG_CL_ORD_ID);
		String tickerName = parseString(message, REG_SYMBOL, SYMBOL);
		OrderSide side = parseSide(message, REG_SIDE, SIDE);
		
		return new CancelRequestMessage(senderCompId, orderId, tickerName, side, origOrderId);
	}
	
	private String getGroupFromRegex(String message, String regex, String tag, int index) throws FIXParseException {
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(message);
		if (!matcher.find())
			throw new FIXParseException(new RejectedMessage(RejectReason.TAG_SPECIFIED_WITHOUT_VALUE.asNumber(), 
					"Cannot find tag <" + tag + "> or value is missed or incorrect"));
		
		String s = matcher.group(index);
		return s;
	}
	
	private MessageType parseMessageType(String message, String regex, String tag) throws FIXParseException {
		String s = getGroupFromRegex(message, regex, tag, 1);
		
		MessageType res;
		try {
			res = MessageType.fromString(s);
		} catch (IllegalArgumentException e) {
			throw new FIXParseException(new RejectedMessage(RejectReason.INVALID_MSG_TYPE.asNumber(), 
					"Message type <" + s + "> is not supported"));
		}
		
		return res;
	}
	private int parseInteger(String message, String regex, String tag) throws FIXParseException {
		String s = getGroupFromRegex(message, regex, tag, 1);
		
		int res;
		try {
			res = Integer.parseInt(s);
		} catch (NumberFormatException e) {
			throw new FIXParseException(new RejectedMessage(RejectReason.INCORRECT_DATA_FORMAT_FOR_VALUE.asNumber(), 
					"Cannot convert value <" + s + "> to integer value by tag <" + tag + ">"));
		}
		
		return res;
	}
	private float parseFloat(String message, String regex, String tag) throws FIXParseException {
		String s = getGroupFromRegex(message, regex, tag, 1);
		
		float res;
		try {
			res = Float.parseFloat(s);
		} catch (NumberFormatException e) {
			throw new FIXParseException(new RejectedMessage(RejectReason.INCORRECT_DATA_FORMAT_FOR_VALUE.asNumber(),
					"Cannot convert value <" + s + "> to float value by tag <" + tag + ">"));			
		}
		
		return res;
	}
	private OrderSide parseSide(String message, String regex, String tag) throws FIXParseException {
		String s = getGroupFromRegex(message, regex, tag, 1);
		
		OrderSide res;
		try {
			res = OrderSide.fromString(s);
		} catch (IllegalArgumentException e) {
			throw new FIXParseException(new RejectedMessage(RejectReason.VALUE_IS_INCORRECT.asNumber(),
					"Order side has unsupported value <" + s + ">"));
		}
		
		return res;
	}
	private OrderStatus parseOrderStatus(String message, String regex, String tag) throws FIXParseException {
		String s = getGroupFromRegex(message, regex, tag, 1);
		
		OrderStatus res;
		try {
			res = OrderStatus.fromString(s);
		} catch (IllegalArgumentException e) {
			throw new FIXParseException(new RejectedMessage(RejectReason.VALUE_IS_INCORRECT.asNumber(),
					"Order status has unsupported value <" + s + ">"));
		}
		
		return res;
	}
	private OrderType parseType(String message, String regex, String tag) throws FIXParseException {
		String s = getGroupFromRegex(message, regex, tag, 1);
		
		OrderType res;
		try {
			res = OrderType.fromString(s);
		} catch (IllegalArgumentException e) {
			throw new FIXParseException(new RejectedMessage(RejectReason.VALUE_IS_INCORRECT.asNumber(),
					"Order type has unsupported value <" + s + ">"));
		}
		
		return res;
	}
	private String parseString(String message, String regex, String tag) throws FIXParseException {
		String s = getGroupFromRegex(message, regex, tag, 1);
		
		return s;
	}
	
	
	public String representMessage(FIXMessage message) {
		switch (message.getType()) {
		case LOGON:return representAsLogon((LogonMessage)message);
		case LOGOUT:return representAsLogout((LogoutMessage)message);
		case NEW_ORDER:return representAsNewOrder((NewOrderMessage)message);
		case EXEC_REPORT:return representAsExecReport((ExecutionReportMessage)message);
		case CANCEL_ORDER:return representAsCancelOrder((CancelRequestMessage)message);
		case REJECT:return representAsReject((RejectedMessage)message);
			default:
				throw new IllegalArgumentException("Message has unsupported type");
		}
	}
	
	private String representClientMessage(ClientMessage message) {
		return 	represent(MSG_TYPE, message.getType()) + FIELD_DELIMITER + 
				represent(SENDER_COMP_ID, message.getSenderCompID());
	}
	
	private String representClientOrderMessage(ClientOrderMessage message) {
		return 	representClientMessage(message) + FIELD_DELIMITER + 
				represent(CL_ORD_ID, message.getOrderID()) + FIELD_DELIMITER +
				represent(SYMBOL, message.getSymbol()) + FIELD_DELIMITER +
				represent(SIDE, message.getSide());
	}

	private String representAsLogon(LogonMessage message) {
		return representClientMessage(message);
	}

	private String representAsLogout(LogoutMessage message) {
		return representClientMessage(message);
	}

	private String representAsNewOrder(NewOrderMessage message) {
		return 	representClientOrderMessage(message) + FIELD_DELIMITER +
				represent(ORDER_QTY, message.getOrderQuantity()) + FIELD_DELIMITER +
				represent(ORD_TYPE, message.getOrderType()) + FIELD_DELIMITER +
				represent(PRICE, message.getPrice());
	}

	private String representAsCancelOrder(CancelRequestMessage message) {
		return 	representClientOrderMessage(message) + FIELD_DELIMITER +
				represent(ORIG_CL_ORD_ID, message.getOrigOrderID());
	}
	
	private String representAsServerMessage(ServerMessage message) {
		return represent(MSG_TYPE, message.getType());
	}

	private String representAsExecutionReportMessage(ExecutionReportMessage message) {
		return 	representAsServerMessage(message) + FIELD_DELIMITER +
				represent(SENDER_COMP_ID, message.getSenderCompID()) + FIELD_DELIMITER +
				represent(CL_ORD_ID, message.getOrderID()) + FIELD_DELIMITER +
				represent(SYMBOL, message.getSymbol()) + FIELD_DELIMITER +
				represent(SIDE, message.getSide());
	}

	private String representAsExecutionRejectMessage(ExecutionRejectMessage message) {
		return 	representAsExecutionReportMessage(message) + FIELD_DELIMITER +
				represent(TEXT, message.getDetail());
	}
	
	private String representAsOkReportMessage(OkReportMessage message) {
		return 	representAsExecutionReportMessage(message) + FIELD_DELIMITER +
				represent(ORDER_QTY, message.getOrderQuantity()) + FIELD_DELIMITER +
				represent(CUM_QTY, message.getCumQuantity()) + FIELD_DELIMITER +
				represent(LEAVES_QTY, message.getLeavesQuantity()) + FIELD_DELIMITER +
				represent(PRICE, message.getPrice());
	}
	
	private String representAsFilledMessage(FilledMessage message) {
		return 	representAsOkReportMessage(message) + FIELD_DELIMITER +
				represent(LAST_PX, message.getLastPrice()) + FIELD_DELIMITER +
				represent(LAST_QTY, message.getLastQuantity()) + FIELD_DELIMITER +
				represent(PARTY_ID, message.getPartyId());
	}
	
	private String representAsCancelledMessage(CancelledMessage message) {
		return 	representAsExecutionReportMessage(message) + FIELD_DELIMITER +
				represent(ORIG_CL_ORD_ID, message.getOrigOrderID()) + FIELD_DELIMITER +
				represent(LAST_PX, message.getLastPrice()) + FIELD_DELIMITER +
				represent(LAST_QTY, message.getLastQuantity());
	}
	
	private String representAsExecReport(ExecutionReportMessage message) {
		String s = "";
		
		switch (message.getOrderStatus()) {
		case NEW: s = representAsOkReportMessage((OkReportMessage)message);
			break;
		case PARTIALLY_FILLED:
		case FILLED: s = representAsFilledMessage((FilledMessage)message);
			break;
		case CANCELLED: s = representAsCancelledMessage((CancelledMessage)message);
			break;
		case REJECTED: s = representAsExecutionRejectMessage((ExecutionRejectMessage)message);
			break;
		default: s = representAsExecutionReportMessage(message);
			break;
		}
		
		s += FIELD_DELIMITER + represent(ORD_STATUS, message.getOrderStatus());
		return s;
	}

	private String representAsReject(RejectedMessage message) {
		return 	representAsServerMessage(message) + FIELD_DELIMITER +
				represent(SESSION_REJECT_REASON, message.getRejectReason()) + FIELD_DELIMITER +
				represent(TEXT, message.getReasonDetail());
	}
	
	private String represent(String tag, MessageType type) {
		return tag + KEY_VALUE_DELIMITER + type.asString();
	}
	
	private String represent(String tag, int value) {
		return tag + KEY_VALUE_DELIMITER + value;
	}
	
	private String represent(String tag, float value) {
		return tag + KEY_VALUE_DELIMITER + value;
	}
	
	private String represent(String tag, OrderSide side) {
		return tag + KEY_VALUE_DELIMITER + side.asString();		
	}
	
	private String represent(String tag, OrderType type) {
		return tag + KEY_VALUE_DELIMITER + type.asString();		
	}
	
	private String represent(String tag, OrderStatus status) {
		return tag + KEY_VALUE_DELIMITER + status.asString();		
	}
	
	private String represent(String tag, String value) {
		return tag + KEY_VALUE_DELIMITER + value;
	}
}
